-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Immutable IoC container for Prism.Autofac.Forms - resolution to issue # 969 #1017
Conversation
Pulling in lastest changes from master Prism repo.
…tes to unit tests.
@ellisnet, It will cover your contributions to all .NET Foundation-managed open source projects. |
@ellisnet, thanks for signing the contribution license agreement. We will now validate the agreement and then the pull request. |
I think nobody is interested in Modules that do not register types. Thus on-demand (that is, late) loading of such a module makes no sense. A better, straightforward, approach IMO would be: Declare up front that on-demand loading is not supported. |
@dvorn I disagree completely. Registration of types in the IoC container is just one thing that you might want to do in your module initialization routine. For example, if your module had something to do with interacting with a SQLite database, you might need to check to see if the database exists or not, and create it if not. But this might never need to happen, based on whether the user uses that function of your app or not. So it makes perfect sense that you would register any types in the IModule.RegisterTypes() method; but do other initialization in the IModule.Initialize() method (e.g. create the database as needed) - and this initialization could still happen OnDemand. |
It would probably have been easier to add a RegisterTypes() method to the IModule definition - that matches how the rest of Prism.Forms works. Have a type to register? Put it in the RegisterTypes() method... |
@ellisnet Please take a look http://prismlibrary.readthedocs.io/en/latest/WPF/04-Modules/ It's for WPF, but ideas are the same.
|
You do not need this method. This method already exists and it is called Initialize. This is the design of Prism. |
@dvorn Thank you. The link that you sent was helpful; I did read through it and it is clear why you would not want to modify the definition of IModule specifically for Prism.Autofac.Forms. Also, it seems like the Autofac implementation for Prism.Forms could have some minor differences from the Unity implementation. So, I would ask that - at least with respect to this "feature" of Prism.Autofac.Forms - the pull request be accepted as-is. As it is programmed, it allows for increased flexibility for module and app developers (vs. requiring them to only do one type of module initialization). Finally, I believe that your suggestion could introduce a new problem: If the Initialize() method for a module was called before the IoC container is built (as is necessary when doing type registration), then no type resolution from the container can occur in the Initialize() method. Module developers could only do type registration or type resolution in the Initialize() method - not both. This will be confusing for new Prism (with Autofac) developers - where telling them they have to do their type registration in a RegisterTypes() method is more straightforward (since that is where they have to do it in other places in Prism.Forms). Thank you for your time and thoughts. |
@dvorn I wanted to add one more comment: So I am not sure if you had a concern that the changes that I made in Prism.Autofac.Forms would be replicated in those other platforms; but at this time they won't be. Sorry for any confusion about that. |
@ellisnet I was wrong stating that you don't need RegisterTypes method, sorry for that. You are absolutely right, immutable containers need two entry points - one for registering services in this module and another one for configuring services in another modules. Conceptually, something like RegisterServices(ContainerBuilder builder)
{
// register types in container
}
ConfigureServices(Container container)
{
// use container to retrieve instances of other services and configure them
} Note however that you still need to forbid on-demand modules. The module registers its types immediately on startup, but the services provided by the module may not be functional until the module performed initialization. Someone may use the services (registered types) before they were initialized properly. |
@dvorn - I addressed the issue of using a ContainerBuilder vs. an AutofacContainer in a response on issue #969 - so no need to rehash that here. So, if you change your RegisterServices() method to RegisterTypes(); and your ConfigureServices() method to Initialize() (which is already defined in IModule); then what you are proposing is what I have submitted. :-) I still do not agree with this statement: "you still need to forbid on-demand modules" You wrote: "Someone may use the services (registered types) before they were initialized properly." I believe that the Prism framework itself handles the on-demand initialization via IModuleManager.LoadModule() - see example. |
@ellisnet As for RegisterServices/ConfigureServices - I wrote "conceptually" and not as some API. On-demand modules: you are just creating a trap for the users to fall into for no particular purpose. They will forget to call LoadModule and use types from the module which are readily available in the container from the very beginning. This will be a source of (sometimes very subtle) bugs. |
@dvorn Understood. Autofac allows for complex registration/resolution logic, so something like this could be done - in RegisterTypes():
This is a much better situation than just saying that they can't create modules to be initialized OnDemand. |
@ellisnet The net result is the same: the type is not usable until you load (initialize) the module, only the symptoms of the failure vary in difficulty to reveal what went wrong. |
@dvorn I disagree. The type may not be usable until you initialize the module (in my example above, it was unusable). There may be registered types that are fine to be used prior to the module being initialized. It depends. |
@ellisnet when Autofac is updated to the latest v4.5 will this PR still be relevant? |
Why, what is supposed to happen with the v4.5 release? |
I completely tested it with Autofac 4.5.0, which has been available on NuGet since the beginning of April. The samples I tested it with are available here |
Just generally asking a question since I'm working to get Prism to support using the latest NetStandard releases |
Apologies in advance if you know all of this already: I tested the PR quite thoroughly with projects that had been upgraded to current .NetStandard goodness. I spent quite a bit of time upgrading each of the various Prism for X.F samples to .NetStandard 1.4 per Oren Novotny's blog and then testing with latest Autofac. Those samples are the ones referenced in my previous comment. |
This is the wrong thread to go into details on NetStandard versioning and should we compile Prism for multiple versions. That said if Prism supported only NetStandard1.4+ you would find it impossible to use Prism unless you first converted your PCL over to NetStandard1.4+ |
Yes. Thank you. I intended only to offer an explanation of why my testing was with 1.4. If someone suggested that this PR also needed to be tested against projects that had been upgraded 1.5 or 1.6, I would find time to do that. |
Nope, I'm just not intimately familiar with Autofac, and wanted to be sure there weren't any breaking changes :) |
I took a quick look at this and I do not feel comfortable having this abstraction around the container. I would prefer if we could just use the container directly. |
@brianlagunas - |
@ellisnet from a consumer standpoint, you are most likely to choose a DI container you are already familiar with, and understand how to work with. Wrapping the container pushes the support for the container from the Autofac community to Prism. |
Or standardize on IServiceProvider akin what Autofac.Extensions.DependencyInjection does, such that modules need not know about the implementation of the service provider. That limits them to use constructor injection only as that is the feature that every DI container supports. I think conditional registration isn't quite often what you want. Rather, you register an interface that your module supports. Then you move your initialization from the Initialize method to a) the factory creating your implementation or b) the constructor of the implementation or c) you follow the factory or provider model, have consumers resolve the factory or provider instead of the feature itself and define that thing to do lazy or async initialization - whatever you want - and yield a properly initialized feature. If it is async and waiting e.g. for UI to be created, but also awaited during initialization, you have a deadlock. But you can test for that and throw a timeout with a proper error message. |
IServiceProvider is not compatible with XF yet. I was looking into this already. I think when XF and Prism moves to .NET Standard this may be something we look into. |
It seems this effort has stalled. I understand that this is a time consuming effort and I do not expect you to continue to meet our requested changes. I will close the PR and may refer to it when the time comes to implement this support. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This request proposes to resolve issue #969 where the ability to update a built Autofac IContainer instance via the ContainerBuilder.Update() method is being removed from the Autofac library; and the current version of Prism.Autofac.Forms - 6.3.0 - relies on this soon-to-be-removed functionality.
The need to update the container is eliminated through the use of an instance of AutofacContainer (which implements a new IAutofacContainer interface). The AutofacContainer type is an abstraction of an Autofac ContainerBuilder plus Container - both of these are wrapped in the AutofacContainer class; with this single type providing all IoC container registration and resolution functions (typically Autofac uses a ContainerBuilder for registration, and a Container for resolution).
PrismApplication
inherits fromPrismApplicationBase<IAutofacContainer>
instead ofPrismApplicationBase<IContainer>
- with changes in the PrismApplication class to ensure that all type registration operations occur before the Autofac container is built; so that it only needs to be built once, and cannot be updated.This new approach also resolves some problems that were observed with Prism.Autofac.Forms version 6.3.0 - where (a) the container seemed unable to resolve an instance of IContainer; and (b) navigation to pages in certain IoC-registered-types appeared to fail (e.g. navigation to pages that were declared in IModule instances).
Breaking changes and/or changes that are required in applications that consume the updated Prism.Autofac.Forms library:
public void RegisterTypes(IContainer container)
becomespublic void RegisterTypes(IAutofacContainer container)
. The new AutofacContainer type implements both IContainer and IAutofacContainer, but the type registration features are only available on IAutofacContainer.So this code:
Becomes simply:
And type registration code like the example above MUST be placed in the overridden
PrismApplication.RegisterTypes()
method, or in a platform-specificIPlatformInitializer.RegisterTypes()
method. These are the correct places to do type registration in a Prism.Forms application, and ensure that the type registration occurs before the container is built (since registrations cannot be added post-container-build).IModule.RegisterTypes()
method.Since most functionality in this proposed change replaces existing functionality in Prism.Autofac.Forms, not too many new unit tests were required; but a test was added to make sure that, once built, the Autofac container used by Prism is immutable (type registration operations throw an exception), and that type resolution still works correctly.
Significant testing was performed with this updated Prism.Autofac.Forms code using the Prism application samples available here - on iOS, Android and UWP devices.